/*
    SEL - Simple DirectMedia Layer Extension Library
    Copyright (C) 2001 Matej Knopp <knopp@users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#ifndef __sel_tokenizer_h__
#define __sel_tokenizer_h__

#include "../base/rwops.h"
#include <stack>
#include <string>
#include <vector>

namespace SEL
{

//! Simple text scanner.
/*!
  This class takes the source RWops and parses the content dividing it
  into tokens. The tokenizer works (internally) with two types of tokens:
  operators and strings. 
  
  The operators are one character long and can be
  specified with the Tokenizer::setOperators () funtion. 
  
  Strings can either be quoted (quoted strings may contain spaces and 
  characters that would normally be considered operators) or unqoted. Quoted 
  strings are determined by double quotes and the format of the content
  is similar to the format of a C strings. Unquoted (plain) strings cannot
  contain white space characters or operators.
 */
class Tokenizer
{
public:
	//! Create a new tokenizer.
	/*!
	  \param rwops the pointer to the initial rwops. The rwops
	  	 will be destroyed automatically within the destructor.
	  \param filename the name of the initial file. The file names
	         are used to detect dead include loop.
	 */
	Tokenizer (RWops *rwops, const std::string & filename);

	//! Destroy the tokenizer.
	/*!
	  \note This also frees all the opened RWops that were supplied
	  to the tokenizer.
	 */
	~Tokenizer ();
	
	//! Push the file into the files stack.
	/*!
	  Using this call you can do "including". After the entire file
	  is processed, it will be automatically popped from the stack
	  and the parsing of the previous file will continue.	  
	  \param ops the rwops to push to the stack.
	  \param the filename this is used to detect the dead include loop.
	  \return true, or false if the file \a filename is already in the
	  files stack.
	 */
	bool pushFile (RWops *ops, const std::string & filename);
	
	//! Pop the file from the files stack.
	/*!
	  Current RWops will be fried.
	  \note There is no need to call this function manually,
	  since it's called automatically after the current file 
	  is processed.
	 */
	bool popFile ();
	
	//! The types of tokens.
	enum 
	{
		TOKEN_INVALID  = -1,
		TOKEN_OPERATOR =  0,
		TOKEN_STRING   =  1
	};

	//! Get the current processed filename.
	const std::string & filename () const	{ return m_filename; }

	//! Get the current operators.
	const std::string & operators () const	{ return m_operators; }

	//! Set the characters that will be considered operators.
	/*!
	  The string accepted is an array of possible operators.
	 */
	void setOperators (const std::string & operators);

	//! Get the current processed line.
	int line () const			{ return m_line; }
	
	//! Get the line of the last processed token.
	int lastLine () const			{ return m_last_line; }
	
	//! Get the number of processed tokens.
	int tokensProcessed () const		{ return m_tokens_processed; }

	//! Get the last processed token's type.
	int tokenType () const			{ return m_token_type; }
	
	//! Get the last processed token's value.
	const std::string & tokenValue () const	{ return m_token_value; }
	
	//! Return if no error has happened.
	bool ok () const			{ return m_ok; }
	
	//! Return if the processed file is ended.
	/*!
	  \note This only applies to the file that is the bottom-most
	  file in the files stack. If there is EOF of other files, they are
	  popped from the stack and the parsing continues with the next
	  file.
	 */
	bool eof () const			{ return m_eof; }	

	//! Return the error description (if any).
	const std::string & error () const	{ return m_error; }
	
	//! Return the type of the next token.
	/*!
	  \note Before querying the attributes of the next token
	  the Tokenizer::peekNextToken () function has to be called.
	 */
	int nextTokenType () const		{ return m_next_token_type; }
	
	//! Return the value of the next token.
	/*!
	  \note Before querying the attributes of the next token
	  the Tokenizer::peekNextToken () function has to be called.
	 */
	const std::string & nextTokenValue () const
						{ return m_next_token_value; }
						
	//! Return whether there is some next token peeked.
	bool haveNextToken () const		{ return m_have_next_token; }
	
	//! Process the next token of the file.
	/*!
	  \return true on success, or false on error or EOF.
	 */
	bool nextToken ();
	
	//! Peek the next token.
	/*!
	  \note this modifies the line and last line values, but it
	  does not modify the error and eof state of the tokenizer.
	  \return true on success, or false on error or EOF.
	 */
	bool peekNextToken ();	

	//! Return whether the character \a ch is an operator.
	bool isOperator (char ch) const;
	
	//! Return whether the character \a ch is a white space.
	static bool isWhiteSpace (char ch);
	
	//! Return whether the character \a ch is a starting character of a
	//! comment.
	bool isComment (char ch) const 		{ return ch == '#'; }

	//! Return whether the string \a ch is a decimal integer.
	static bool isDecInteger (const char *ch);
	
	//! Return whether the string \a ch is a hexadecimal integer.
	static bool isHexInteger (const char *ch, bool have_prefix = true);
	
	//! Return whether the string \a ch is a floating point number.
	static bool isFloat (const char *ch);

protected:
	std::vector<std::string> m_filenames;
	std::string m_filename;
	
	std::stack<RWops *> m_files;
	RWops *m_file;
	
	std::stack<int> m_lines;
	int m_line;
	
	std::stack<int> m_last_lines;
	int m_last_line;
	
	int m_tokens_processed;
	
	int m_token_type;
	std::string m_token_value;	
	bool m_ok;
	std::string m_error;
	
	int m_next_token_type;
	std::string m_next_token_value;
	bool m_have_next_token;
	bool m_next_token_res;
	std::string m_next_error;
	bool m_next_ok;
	bool m_next_eof;
	
	std::string m_operators;
	
	void skipWhiteSpaces ();
	void skipLine ();

	bool m_eof;

	char getChar (); // virtual?
	void putChar ();

	bool processNextToken (int &type, std::string &value);
};

} // namespace SEL

#endif // __sel_tokenizer__
